﻿Namespace Othello
    ''' <summary>
    ''' Summary description for Board.
    ''' </summary>
    Public Class Board
        ' These constants represent the contents of a board square.
        Public Shared ReadOnly Black As Integer = -1
        Public Shared ReadOnly Empty As Integer = 0
        Public Shared ReadOnly White As Integer = 1

        ' These counts reflect the current board situation.
        Public ReadOnly Property BlackCount() As Integer
            Get
                Return Me.m_blackCount
            End Get
        End Property
        Public ReadOnly Property WhiteCount() As Integer
            Get
                Return Me.m_whiteCount
            End Get
        End Property
        Public ReadOnly Property EmptyCount() As Integer
            Get
                Return Me.m_emptyCount
            End Get
        End Property
        Public ReadOnly Property BlackFrontierCount() As Integer
            Get
                Return Me.m_blackFrontierCount
            End Get
        End Property
        Public ReadOnly Property WhiteFrontierCount() As Integer
            Get
                Return Me.m_whiteFrontierCount
            End Get
        End Property
        Public ReadOnly Property BlackSafeCount() As Integer
            Get
                Return Me.m_blackSafeCount
            End Get
        End Property
        Public ReadOnly Property WhiteSafeCount() As Integer
            Get
                Return Me.m_whiteSafeCount
            End Get
        End Property

        ' Internal counts.
        Private m_blackCount As Integer
        Private m_whiteCount As Integer
        Private m_emptyCount As Integer
        Private m_blackFrontierCount As Integer
        Private m_whiteFrontierCount As Integer
        Private m_blackSafeCount As Integer
        Private m_whiteSafeCount As Integer

        ' This two-dimensional array represents the squares on the board.
        Private squares As Integer(,)

        ' This two-dimensional array tracks which discs are safe (i.e.,
        ' discs that cannot be outflanked in any direction).
        Private safeDiscs As Boolean(,)

        '
        ' Creates a new, empty Board object.
        '
        Public Sub New()
            '
            ' TODO: Add constructor logic here
            '

            ' Create the squares and safe disc map.
            Me.squares = New Integer(7, 7) {}
            Me.safeDiscs = New Boolean(7, 7) {}

            ' Clear the board and map.
            Dim i As Integer, j As Integer
            For i = 0 To 7
                For j = 0 To 7
                    Me.squares(i, j) = Board.Empty
                    Me.safeDiscs(i, j) = False
                Next
            Next

            ' Update the counts.
            Me.UpdateCounts()
        End Sub

        '
        ' Creates a new Board object by copying an existing one.
        '
        Public Sub New(ByVal board__1 As Board)
            ' Create the squares and map.
            Me.squares = New Integer(7, 7) {}
            Me.safeDiscs = New Boolean(7, 7) {}

            ' Copy the given board.
            Dim i As Integer, j As Integer
            For i = 0 To 7
                For j = 0 To 7
                    Me.squares(i, j) = board__1.squares(i, j)
                    Me.safeDiscs(i, j) = board__1.safeDiscs(i, j)
                Next
            Next

            ' Copy the counts.
            Me.m_blackCount = board__1.BlackCount
            Me.m_whiteCount = board__1.WhiteCount
            Me.m_emptyCount = board__1.EmptyCount
            Me.m_blackSafeCount = board__1.BlackSafeCount
            Me.m_whiteSafeCount = board__1.WhiteSafeCount
        End Sub

        '
        ' Sets a board with the initial game set-up.
        '
        Public Sub SetForNewGame()
            ' Clear the board.
            Dim i As Integer, j As Integer
            For i = 0 To 7
                For j = 0 To 7
                    Me.squares(i, j) = Board.Empty
                    Me.safeDiscs(i, j) = False
                Next
            Next

            ' Set two black and two white discs in the center.
            Me.squares(3, 3) = White
            Me.squares(3, 4) = Black
            Me.squares(4, 3) = Black
            Me.squares(4, 4) = White

            ' Update the counts.
            Me.UpdateCounts()
        End Sub

        '
        ' Returns the contents of a given board square.
        '
        Public Function GetSquareContents(ByVal row As Integer, ByVal col As Integer) As Integer
            Return Me.squares(row, col)
        End Function

        '
        ' Places a disc for the player on the board and flips any outflanked
        ' opponents.
        ' Note: For performance reasons, it does NOT check that the move is
        ' valid.
        '
        Public Sub MakeMove(ByVal color As Integer, ByVal row As Integer, ByVal col As Integer)
            ' Set the disc on the square.
            Me.squares(row, col) = color

            ' Flip any flanked opponents.
            Dim dr As Integer, dc As Integer
            Dim r As Integer, c As Integer
            For dr = -1 To 1
                For dc = -1 To 1
                    ' Are there any outflanked opponents?
                    If Not (dr = 0 AndAlso dc = 0) AndAlso IsOutflanking(color, row, col, dr, dc) Then
                        r = row + dr
                        c = col + dc
                        ' Flip 'em.
                        While Me.squares(r, c) = -color
                            Me.squares(r, c) = color
                            r += dr
                            c += dc
                        End While
                    End If
                Next
            Next

            ' Update the counts.
            Me.UpdateCounts()
        End Sub

        '
        ' Determines if the player can make any valid move on the board.
        '
        Public Function HasAnyValidMove(ByVal color As Integer) As Boolean
            ' Check all board positions for a valid move.
            Dim r As Integer, c As Integer
            For r = 0 To 7
                For c = 0 To 7
                    If Me.IsValidMove(color, r, c) Then
                        Return True
                    End If
                Next
            Next

            ' None found.
            Return False
        End Function

        '
        ' Determines if a specific move is valid for the player.
        '
        Public Function IsValidMove(ByVal color As Integer, ByVal row As Integer, ByVal col As Integer) As Boolean
            ' The square must be empty.
            If Me.squares(row, col) <> Board.Empty Then
                Return False
            End If

            ' Must be able to flip at least one opponent disc.
            Dim dr As Integer, dc As Integer
            For dr = -1 To 1
                For dc = -1 To 1
                    If Not (dr = 0 AndAlso dc = 0) AndAlso Me.IsOutflanking(color, row, col, dr, dc) Then
                        Return True
                    End If
                Next
            Next

            ' No opponents could be flipped.
            Return False
        End Function

        '
        ' Returns the number of valid moves a player can make on the board.
        '
        Public Function GetValidMoveCount(ByVal color As Integer) As Integer
            Dim n As Integer = 0

            ' Check all board positions.
            Dim i As Integer, j As Integer
            For i = 0 To 7
                For j = 0 To 7
                    ' If the move is valid for the color, bump the count.
                    If Me.IsValidMove(color, i, j) Then
                        n += 1
                    End If
                Next
            Next
            Return n
        End Function

        '
        ' Given a player move and a specific direction, determines if any
        ' opponent discs will be outflanked.
        ' Note: For performance reasons the direction values are NOT checked
        ' for validity (dr and dc may be one of -1, 0 or 1 but both should
        ' not be zero).
        '
        Private Function IsOutflanking(ByVal color As Integer, ByVal row As Integer, ByVal col As Integer, ByVal dr As Integer, ByVal dc As Integer) As Boolean
            ' Move in the given direction as long as we stay on the board and
            ' land on a disc of the opposite color.
            Dim r As Integer = row + dr
            Dim c As Integer = col + dc
            While r >= 0 AndAlso r < 8 AndAlso c >= 0 AndAlso c < 8 AndAlso Me.squares(r, c) = -color
                r += dr
                c += dc
            End While

            ' If we ran off the board, only moved one space or didn't land on
            ' a disc of the same color, return false.
            If r < 0 OrElse r > 7 OrElse c < 0 OrElse c > 7 OrElse (r - dr = row AndAlso c - dc = col) OrElse Me.squares(r, c) <> color Then
                Return False
            End If

            ' Otherwise, return true;
            Return True
        End Function

        '
        ' Updates the board counts and safe disc map.
        ' Note: MUST be called after any changes to the board contents.
        '
        Private Sub UpdateCounts()
            ' Reset all counts.
            Me.m_blackCount = 0
            Me.m_whiteCount = 0
            Me.m_emptyCount = 0
            Me.m_blackFrontierCount = 0
            Me.m_whiteFrontierCount = 0
            Me.m_whiteSafeCount = 0
            Me.m_blackSafeCount = 0

            Dim i As Integer, j As Integer

            ' Update the safe disc map.
            '
            ' All currently unsafe discs are checked to see if they are still
            ' outflankable. Those that are not are marked as safe.
            ' If any new safe discs were found, the process is repeated
            ' because this change may have made other discs safe as well. The
            ' loop exits when no new safe discs are found.
            Dim statusChanged As Boolean = True
            While statusChanged
                statusChanged = False
                For i = 0 To 7
                    For j = 0 To 7
                        If Me.squares(i, j) <> Board.Empty AndAlso Not Me.safeDiscs(i, j) AndAlso Not Me.IsOutflankable(i, j) Then
                            Me.safeDiscs(i, j) = True
                            statusChanged = True
                        End If
                    Next
                Next
            End While

            ' Tally the counts.
            Dim dr As Integer, dc As Integer
            For i = 0 To 7
                For j = 0 To 7
                    ' If there is a disc at this position, determine if it is
                    ' on the frontier (i.e., adjacent to an empty square).
                    Dim isFrontier As Boolean = False
                    If Me.squares(i, j) <> Board.Empty Then
                        For dr = -1 To 1
                            For dc = -1 To 1
                                If Not (dr = 0 AndAlso dc = 0) AndAlso i + dr >= 0 AndAlso i + dr < 8 AndAlso j + dc >= 0 AndAlso j + dc < 8 AndAlso Me.squares(i + dr, j + dc) = Board.Empty Then
                                    isFrontier = True
                                End If
                            Next
                        Next
                    End If

                    ' Update the counts.
                    If Me.squares(i, j) = Board.Black Then
                        Me.m_blackCount += 1
                        If isFrontier Then
                            Me.m_blackFrontierCount += 1
                        End If
                        If Me.safeDiscs(i, j) Then
                            Me.m_blackSafeCount += 1
                        End If
                    ElseIf Me.squares(i, j) = Board.White Then
                        Me.m_whiteCount += 1
                        If isFrontier Then
                            Me.m_whiteFrontierCount += 1
                        End If
                        If Me.safeDiscs(i, j) Then
                            Me.m_whiteSafeCount += 1
                        End If
                    Else
                        Me.m_emptyCount += 1
                    End If
                Next
            Next
        End Sub

        '
        ' Returns true if the disc at the given position can be outflanked in
        ' any direction.
        ' Note: For performance reasons we do NOT check that the square has a
        ' disc.
        '
        Private Function IsOutflankable(ByVal row As Integer, ByVal col As Integer) As Boolean
            ' Get the disc color.
            Dim color As Integer = Me.squares(row, col)

            ' Check each line through the disc.
            ' NOTE: A disc is outflankable if there is an empty square on
            ' both sides OR if there is an empty square on one side and an
            ' opponent or unsafe (outflankable) disc of the same color on the
            ' other side.
            Dim i As Integer, j As Integer
            Dim hasSpaceSide1 As Boolean, hasSpaceSide2 As Boolean
            Dim hasUnsafeSide1 As Boolean, hasUnsafeSide2 As Boolean

            ' Check the horizontal line through the disc.
            hasSpaceSide1 = False
            hasUnsafeSide1 = False
            hasSpaceSide2 = False
            hasUnsafeSide2 = False
            ' West side.
            j = 0
            While j < col AndAlso Not hasSpaceSide1
                If Me.squares(row, j) = Board.Empty Then
                    hasSpaceSide1 = True
                ElseIf Me.squares(row, j) <> color OrElse Not Me.safeDiscs(row, j) Then
                    hasUnsafeSide1 = True
                End If
                j += 1
            End While
            ' East side.
            j = col + 1
            While j < 8 AndAlso Not hasSpaceSide2
                If Me.squares(row, j) = Board.Empty Then
                    hasSpaceSide2 = True
                ElseIf Me.squares(row, j) <> color OrElse Not Me.safeDiscs(row, j) Then
                    hasUnsafeSide2 = True
                End If
                j += 1
            End While
            If (hasSpaceSide1 AndAlso hasSpaceSide2) OrElse (hasSpaceSide1 AndAlso hasUnsafeSide2) OrElse (hasUnsafeSide1 AndAlso hasSpaceSide2) Then
                Return True
            End If

            ' Check the vertical line through the disc.
            hasSpaceSide1 = False
            hasSpaceSide2 = False
            hasUnsafeSide1 = False
            hasUnsafeSide2 = False
            ' North side.
            i = 0
            While i < row AndAlso Not hasSpaceSide1
                If Me.squares(i, col) = Board.Empty Then
                    hasSpaceSide1 = True
                ElseIf Me.squares(i, col) <> color OrElse Not Me.safeDiscs(i, col) Then
                    hasUnsafeSide1 = True
                End If
                i += 1
            End While
            ' South side.
            i = row + 1
            While i < 8 AndAlso Not hasSpaceSide2
                If Me.squares(i, col) = Board.Empty Then
                    hasSpaceSide2 = True
                ElseIf Me.squares(i, col) <> color OrElse Not Me.safeDiscs(i, col) Then
                    hasUnsafeSide2 = True
                End If
                i += 1
            End While
            If (hasSpaceSide1 AndAlso hasSpaceSide2) OrElse (hasSpaceSide1 AndAlso hasUnsafeSide2) OrElse (hasUnsafeSide1 AndAlso hasSpaceSide2) Then
                Return True
            End If

            ' Check the Northwest-Southeast diagonal line through the disc.
            hasSpaceSide1 = False
            hasSpaceSide2 = False
            hasUnsafeSide1 = False
            hasUnsafeSide2 = False
            ' Northwest side.
            i = row - 1
            j = col - 1
            While i >= 0 AndAlso j >= 0 AndAlso Not hasSpaceSide1
                If Me.squares(i, j) = Board.Empty Then
                    hasSpaceSide1 = True
                ElseIf Me.squares(i, j) <> color OrElse Not Me.safeDiscs(i, j) Then
                    hasUnsafeSide1 = True
                End If
                i -= 1
                j -= 1
            End While
            ' Southeast side.
            i = row + 1
            j = col + 1
            While i < 8 AndAlso j < 8 AndAlso Not hasSpaceSide2
                If Me.squares(i, j) = Board.Empty Then
                    hasSpaceSide2 = True
                ElseIf Me.squares(i, j) <> color OrElse Not Me.safeDiscs(i, j) Then
                    hasUnsafeSide2 = True
                End If
                i += 1
                j += 1
            End While
            If (hasSpaceSide1 AndAlso hasSpaceSide2) OrElse (hasSpaceSide1 AndAlso hasUnsafeSide2) OrElse (hasUnsafeSide1 AndAlso hasSpaceSide2) Then
                Return True
            End If

            ' Check the Northeast-Southwest diagonal line through the disc.
            hasSpaceSide1 = False
            hasSpaceSide2 = False
            hasUnsafeSide1 = False
            hasUnsafeSide2 = False
            ' Northeast side.
            i = row - 1
            j = col + 1
            While i >= 0 AndAlso j < 8 AndAlso Not hasSpaceSide1
                If Me.squares(i, j) = Board.Empty Then
                    hasSpaceSide1 = True
                ElseIf Me.squares(i, j) <> color OrElse Not Me.safeDiscs(i, j) Then
                    hasUnsafeSide1 = True
                End If
                i -= 1
                j += 1
            End While
            ' Southwest side.
            i = row + 1
            j = col - 1
            While i < 8 AndAlso j >= 0 AndAlso Not hasSpaceSide2
                If Me.squares(i, j) = Board.Empty Then
                    hasSpaceSide2 = True
                ElseIf Me.squares(i, j) <> color OrElse Not Me.safeDiscs(i, j) Then
                    hasUnsafeSide2 = True
                End If
                i += 1
                j -= 1
            End While
            If (hasSpaceSide1 AndAlso hasSpaceSide2) OrElse (hasSpaceSide1 AndAlso hasUnsafeSide2) OrElse (hasUnsafeSide1 AndAlso hasSpaceSide2) Then
                Return True
            End If

            ' All lines are safe so the disc cannot be outflanked.
            Return False
        End Function
    End Class
End Namespace
